function log(title, content ) {
  console.log(
    `%c ${title}  %c ${content} %c `,
    'background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;  color: #fff',
    'background:#41b883 ; padding: 1px; border-radius: 0 3px 3px 0;  color: #fff',
    'background:transparent'
  )
}

/**
 * 封装出来一个公共的 FileReader 方法
 */
class FileReaderSecondry{
  constructor(file, readMethods, loadFn, errorFn) {
    if (!(file instanceof Blob)) {
      return console.error('file 应该为 Blob 或者 File 类型')
    }
    if (typeof loadFn !== 'function') {
      return console.error('请设置回调函数：loadFn')
    }
    this.reader = new FileReader()
    this.file = file
    this.loadFn = loadFn
    this.errorFn = errorFn || console.error
    this.progressFn = function (e) {
      const {loaded, total}  = e
      const progress = parseInt(loaded / total * 100)
      log('progress', progress)
    }
    this.init()
    this.readFile(readMethods)
  }
  /**
   * 初始化 fileReader 实例
   */
  init() {
    this.reader.addEventListener('load', this.loadFn)
    this.reader.addEventListener('progress', this.progressFn)
    this.reader.addEventListener('error',  this.errorFn)
  }

  /**
   * 开始读取文件
   */
  readFile(readMethods) {
    switch (readMethods) {
      case 'readAsArrayBuffer':
        this.reader.readAsArrayBuffer(this.file)
        break
    }

  }
  /**
   * 读取完成 销毁当前的 fileReader 实例
   */
  destroy() {
    this.reader.removeEventListener('load', this.loadFn)
    this.reader.removeEventListener('progress', this.progressFn)
    this.reader.removeEventListener('error',  this.errorFn)
    this.reader = null
  }
}

/**
 * 获取时间差
 */
class TimeTemp {
  static instance = null
  static create() {
    return this.instance ? this.instance : new TimeTemp()
  }
  constructor() {
    this.startTemp = 0
    this.endTemp = 0
    this.temp = 0
  }
  /**
   * 开始计时
   */
  start() {
    this.startTemp = Date.now()
  }
  /**
   * 结束计时
   */
  end() {
    if (this.startTemp === 0) {
      return console.error('请先触发记录开始时间方法： start')
    }
    this.endTemp = Date.now()
  }
  /**
   * 获取到时间差
   * @returns {string} 返回时间差
   */
  getTemp() {
    if (this.startTemp === 0 && this.endTemp === 0) {
      return console.error('请先记录下时间：start 与 end')
    }
    // 计算时间差的时间戳
    this.temp = ((this.endTemp - this.startTemp) / 1000).toFixed(2)
    // 重置 开始时间与结束时间
    this.startTemp = 0
    this.endTemp = 0
    return this.temp
  }
  /**
   * 直接打印时间差
   */
  showTemp() {
    log(`用时为`, this.getTemp())
  }
}
/**
 * 1.直接使用一个队列来做 md5 转码
 * 原因如下：
 * FileReader.prototype.readAsArrayBuffer方法是异步的
 * 因为异步，无法保证读取的load回调
 * 所以无法保证arrayBuffer.append(chunk)按照正确顺序将插入
 *
 * 2.这里使用一个订阅-发布者模式 event 来监听是否转码完成
 * 原因如下：
 * 由于FileReader.prototype.readAsArrayBuffer方法是异步的
 * 但是 worker.js 中的代码是同步的
 * 所以在 worker 中直接获取 md5Queue.md5 所获取的值为空
 * 因此放一个 订阅-发布者 来抛出md5值
 */
class Md5Queue{
  constructor(md5DoneFn) {
    // 最后获取的md5值
    this.md5 = ''
    // 分片的队列
    this.queue = []
    // 记录时间
    this.timeTemp = TimeTemp.create()
    // 注册一个订阅-发布者
    this.event = new Event()
    this.event.$on('md5Done', md5DoneFn)
    this.sparkArrayBuffer = new SparkMD5.ArrayBuffer()
  }
  // 插入队列
  push(chunck) {
    console.log(chunck, 'readAsArrayBuffer');
    if (!(chunck instanceof Blob)) {
      return console.error('chunck 应该为 Blob 或者 File 类型')
    }
    this.queue.push(chunck)
  }
  // 弹出队列
  shift() {
    return this.queue.shift()
  }
  // 计算md5
  calcMd5() {
    // 记录下开始时间
    if (!this.timeTemp.startTemp) this.timeTemp.start()
    const chunck = this.shift()
    // 检查是否已经将队列消耗完成
    // 没有完成则继续插入 chunk 到 spark-md5 ArrayBuffer 实例中
    // 完成队列消耗则计算 md5 并且 通过 event 通知到worker中
    if (chunck) {
      const fileReader = new FileReaderSecondry(
        chunck,
        'readAsArrayBuffer',
        ({ target: { result} }) => {
          console.log(result, 'readAsArrayBuffer-result');
          this.sparkArrayBuffer.append(result)
          // 递归 将文件全部插入到 spark-md5 ArrayBuffer 中
          this.calcMd5()
          // 销毁掉当前这个 fileReader
          fileReader.destroy()
        }
      )
    } else {
      this.md5 = this.sparkArrayBuffer.end()
      // 记录下结束时间
      this.timeTemp.end()
      // 展示出来全部时间
      this.timeTemp.showTemp()
      log('计算出来的md5：', this.md5)
      // 触发 订阅-发布模式 来完成通知
      this.event.$emit('md5Done', this.md5)
    }
  }
}

/**
 * 订阅-发布模式
 * 模仿使用vue的事件中心源码来写
 */
class Event {
  constructor() {
    this._event = {}
  }
  $on(evt, cb) {
    if (this._event[evt]) {
      this._event[evt].push(cb)
    } else {
      this._event[evt] = [cb]
    }
  }
  $emit(evt, ...arg) {
    if (!this._event[evt]) {
      return console.error(`没有当前注册的事件，请先注册该事件${evt}`)
    }
    for (const cb of this._event[evt]) {
      cb.apply(this, arg)
    }
  }
}
